﻿using Kernelx.Contexts.Defaults;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Kernelx.Extensions._Internals
{
    internal class Mapper : IMapper
    {
        private readonly Dictionary<string, IList<PropertyInfo>> _container = new Dictionary<string, IList<PropertyInfo>>();
        private IList<PropertyInfo> GetSourceProp(Type ttType, string propName, Type tsType)
        {
            var key = $"{ttType.FullName}.{propName}-->{tsType.FullName}";
            if (!_container.ContainsKey(key))
            {
                lock (_container)
                    if (!_container.ContainsKey(key))
                    {
                        //查找目标属性名在源类型中的对应属性
                        IList<PropertyInfo> props = new List<PropertyInfo>();
                        var tsProps = tsType.GetProperties().Where(x => x.CanRead).ToList();
                        var p = tsProps.FirstOrDefault(n => n.Name == propName);
                        if (p != null)
                            props.Add(p);
                        else if ((p = tsProps.FirstOrDefault(n => propName.StartsWith(n.Name))) != null)
                        {
                            //尝试从导航属性中查找对应属性
                            propName = propName.Replace(p.Name, "");
                            var subProp = p.PropertyType.GetProperties().FirstOrDefault(x => x.CanRead && x.Name == propName);
                            if (subProp != null)
                            {
                                props.Add(p);
                                props.Add(subProp);
                            }
                        }
                        _container[key] = props;
                    }
            }
            return _container[key];
        }

        public TT MapTo<TS, TT>(TS source, TT target, bool skipEmpty = false, Func<TT, object> ignoreProps = null)
            where TS : class
            where TT : class
        {
            var tsType = source.GetType();
            var ttType = typeof(TT) == typeof(object) ? target.GetType() : typeof(TT);
            var ttProps = ttType.GetProperties().Where(x => x.CanRead).ToList();
            IList<string> ignorePNames = ignoreProps != null ? ignoreProps(target).GetType().GetProperties().Select(x => x.Name).ToList() : new List<string>();

            foreach (var ttp in ttProps)
            {
                if (ignorePNames.Contains(ttp.Name))
                    continue;

                var tsProps = GetSourceProp(ttType, ttp.Name, tsType);
                object v = null;
                PropertyInfo stp = null;
                if (tsProps.Count == 1)
                    v = (stp = tsProps[0]).GetValue(source, stp.GetIndexValues());
                else if (tsProps.Count == 2)
                {
                    var sub = tsProps[0].GetValue(source, stp.GetIndexValues());
                    if (sub != null)
                        v = (stp = tsProps[1]).GetValue(sub, stp.GetIndexValues());
                }

                if (ttp.PropertyType == typeof(string) && !skipEmpty || v != null)
                    ttp.SetValue(target, v, ttp.GetIndexValues());

            }
            return target;
        }

    }
}
